//
// Created by denglibin on 19-5-9.
//


// Created by denglibin on 19-5-8.
// 矩阵 变换
//
#include "settings.h"
#include "shader.h"
#include "buffer_obj.h"
#include "init.h"
#include "call_back.h"
#include "texture.h"
#include <GLFW/glfw3.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <unistd.h>
#include "Camera.h"
//坐标系统,3d

/**
5个不同的坐标系统：

局部空间(Local Space，或者称为物体空间(Object Space))
 --(模型矩阵)->
世界空间(World Space)
 --(观察矩阵)-->
观察空间(View Space，或者称为视觉空间(Eye Space))
 --(投影矩阵(透视投影矩阵))-->
裁剪空间(Clip Space)
屏幕空间(Screen Space)

 */

static Camera camera; //创建摄像机对象
static float fov = 45.0f;//视野范围 度
static float deltaTime = 0.0f; // 当前帧与上一帧的时间差
static float lastFrame = 0.0f; // 上一帧的时间

static glm::vec3 lightPos = glm::vec3(1.2f, 1.0f, 2.0f); //灯位置
static  glm::vec3 lightColor; //灯颜色

/**
 * 键盘输入事件
 * @param window
 */
static void processInput5(GLFWwindow *window){
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) //esc键 关闭窗口
        glfwSetWindowShouldClose(window, GL_TRUE);
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS){
        camera.moveFront(deltaTime); //摄像机前进
    }
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        camera.moveBack(deltaTime); //摄像机后退

    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS){
        camera.moveLeft(deltaTime); //摄像机左移
    }
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        camera.moveRight(deltaTime); //摄像机右移
    if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS)
        camera.moveUp(deltaTime); //摄像机上移
    if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS)
        camera.moveDown(deltaTime); //摄像机下移
}

/**
 * 鼠标输入事件
 * @param window
 * @param xpos
 * @param ypos
 */
static void mouse_callback(GLFWwindow* window, double xpos, double ypos){
    camera.updateFront(xpos, ypos);
}

/**
 * 滚轮改变视野大小
 * @param window
 * @param xoffset
 * @param yoffset
 */
static void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    if(fov >= 1.0f && fov <= 45.0f)
        fov -= yoffset;
    if(fov <= 1.0f)
        fov = 1.0f;
    if(fov >= 45.0f)
        fov = 45.0f;
}


/**
 * 设置着色器程序变量的值
 * @param shaderProgramId 着色器程序id
 * @param i 第几个立方体
 */
static void setObjectUniformValue(int shaderProgramId){
    //1.模型矩阵 应用到所有物体的顶点上，以变换它们到全局的世界空间
    glm::mat4 model = glm::mat4(1.0f); //单位矩阵(变换矩阵)
    //2 通过摄像机来获取观察矩阵
    glm::mat4 view = camera.getViewMatrix();
    //3.透视投影矩阵
    glm::mat4 projection = glm::mat4(1.0f);
    /**
     * 它的第一个参数定义了fov的值，它表示的是视野(Field of View)，并且设置了观察空间的大小。如果想要一个真实的观察效果，它的值通常设置为45.0f，但想要一个末日风格的结果你可以将其设置一个更大的值。
     * 第二个参数设置了宽高比，由视口的宽除以高所得。
     * 第三和第四个参数设置了平截头体的近和远平面。我们通常设置近距离为0.1f，而远距离设为100.0f
     */
    projection = glm::perspective(glm::radians(fov), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);

    //设置uniform变量的值
    glUniformMatrix4fv(glGetUniformLocation(shaderProgramId, "model"), 1, GL_FALSE, glm::value_ptr(model));
    glUniformMatrix4fv(glGetUniformLocation(shaderProgramId, "view"), 1, GL_FALSE, glm::value_ptr(view));
    glUniformMatrix4fv(glGetUniformLocation(shaderProgramId, "projection"), 1, GL_FALSE, glm::value_ptr(projection));



    setVec3(shaderProgramId,"lightColor",  1.0f, 1.0f, 1.0f); //灯颜色
    setVec3(shaderProgramId,"objectColor", 1.0f, 0.5f, 0.31f); //物体颜色
    setVec3(shaderProgramId,"lightPos",  lightPos.x,lightPos.y, lightPos.z); //灯的位置

    //观察者(摄像机)位置
    setVec3(shaderProgramId, "viewPos", camera.cameraPos.x, camera.cameraPos.y, camera.cameraPos.z);

    //材质设置
    setVec3(shaderProgramId,"material.ambient",  1.0f, 0.5f, 0.31f);
    setVec3(shaderProgramId,"material.diffuse",  1.0f, 0.5f, 0.31f);
    setVec3(shaderProgramId,"material.specular", 0.5f, 0.5f, 0.5f);
    setFloat(shaderProgramId,"material.shininess", 32.0f);
    //光强度设置
    setVec3(shaderProgramId,"light.ambient",  0.2f, 0.2f, 0.2f);
    setVec3(shaderProgramId,"light.diffuse",  0.5f, 0.5f, 0.5f); // 将光照调暗了一些以搭配场景
    setVec3(shaderProgramId,"light.specular", 1.0f, 1.0f, 1.0f);


    //动态改变灯的颜色
    lightColor.x = sin(glfwGetTime() * 2.0f);
    lightColor.y = sin(glfwGetTime() * 0.7f);
    lightColor.z = sin(glfwGetTime() * 1.3f);
    setVec3(shaderProgramId,"lightColor",  lightColor.x, lightColor.y, lightColor.z); //灯颜色
    glm::vec3 diffuseColor = lightColor   * glm::vec3(0.5f); // 降低影响
    glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f); // 很低的影响
    setVec3(shaderProgramId,"light.ambient", ambientColor.x, ambientColor.y, ambientColor.z);
    setVec3(shaderProgramId,"light.diffuse", diffuseColor.x,diffuseColor.y,diffuseColor.z);

}

/**
 * 设置着色器程序变量的值
 * @param shaderProgramId 着色器程序id
 * @param i 第几个立方体
 */
static void setLightUniformValue(int shaderProgramId){
    //1.模型矩阵 应用到所有物体的顶点上，以变换它们到全局的世界空间
    glm::mat4 model = glm::mat4(1.0f); //单位矩阵(变换矩阵)
    model = glm::translate(model, lightPos);
    model = glm::scale(model, glm::vec3(0.2f));
    //2 通过摄像机来获取观察矩阵
    glm::mat4 view = camera.getViewMatrix();
    //3.透视投影矩阵
    glm::mat4 projection = glm::mat4(1.0f);
    /**
     * 它的第一个参数定义了fov的值，它表示的是视野(Field of View)，并且设置了观察空间的大小。如果想要一个真实的观察效果，它的值通常设置为45.0f，但想要一个末日风格的结果你可以将其设置一个更大的值。
     * 第二个参数设置了宽高比，由视口的宽除以高所得。
     * 第三和第四个参数设置了平截头体的近和远平面。我们通常设置近距离为0.1f，而远距离设为100.0f
     */
    projection = glm::perspective(glm::radians(fov), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);

    //设置uniform变量的值
    //各个变换矩阵
    glUniformMatrix4fv(glGetUniformLocation(shaderProgramId, "model"), 1, GL_FALSE, glm::value_ptr(model));
    glUniformMatrix4fv(glGetUniformLocation(shaderProgramId, "view"), 1, GL_FALSE, glm::value_ptr(view));
    glUniformMatrix4fv(glGetUniformLocation(shaderProgramId, "projection"), 1, GL_FALSE, glm::value_ptr(projection));

    setVec3(shaderProgramId,"lightColor",  lightColor.x, lightColor.y, lightColor.z); //灯颜色





}

static void show(){
    //顶点数据
    // 要想渲染一个立方体，我们一共需要36个顶点（6个面 x 每个面有2个三角形组成 x 每个三角形有3个顶点）
    float vertices[] = {
            //   ---- 位置 ----   ----法向量------
            -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
            0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
            0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
            0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
            -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
            -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,

            -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
            0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
            0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
            0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
            -0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
            -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,

            -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
            -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
            -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
            -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
            -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
            -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,

            0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
            0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
            0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
            0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
            0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
            0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,

            -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
            0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
            0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
            0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
            -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
            -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,

            -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
            0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
            0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
            0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
            -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
            -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f
    };

    //初始化
    init();
    //创建窗口对象
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, TITLE, NULL, NULL);
    if (window == NULL)
    {
        printf("Failed to create GLFW window");
        glfwTerminate();
        exit(-1);
    }
    //通知GLFW将窗口的上下文设置为当前线程的主上下文
    glfwMakeContextCurrent(window);
    //对窗口注册一个回调函数,每当窗口改变大小，GLFW会调用这个函数并填充相应的参数供你处理
    glfwSetFramebufferSizeCallback(window, frameSizeChangeCallBack);
    glfwSetCursorPosCallback(window, mouse_callback); //鼠标捕捉
    //隐藏光标，并捕捉(Capture)它 在调用这个函数之后，无论我们怎么去移动鼠标，光标都不会显示了，它也不会离开窗口
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    glfwSetScrollCallback(window, scroll_callback); //滚轮事件

    //初始化GLAD用来管理OpenGL的函数指针
    //GLFW给我们的是glfwGetProcAdress，它根据我们编译的系统定义了正确的函数
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        printf("Failed to initialize GLAD");
        exit(-1);
    }
    //在开始渲染之前必须告诉OpenGL渲染窗口(Viewport)的尺寸大小，这样OpenGL才能知道怎样根据窗口大小显示数据和坐标。
    glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);

    //创建物体着色器程序
    ShaderProgram objectShader = {0, "shader_code/vertex05.vs", "shader_code/fragment05object.fs"};
    createShaderProgram(&objectShader);
    //灯光着色器程序
    ShaderProgram lightShader = {0, "shader_code/vertex05.vs", "shader_code/fragment05light.fs"};
    createShaderProgram(&lightShader);

    //创建缓冲顶点缓冲对象
    unsigned  int VBO;
    glGenBuffers(1, &VBO); //创建一个缓冲对象,把id设置到VBO中

    //创建物体顶点数组对象
    unsigned int objectVAO; //顶点属性数组对象id
    glGenVertexArrays(1, &objectVAO); //创建顶点数组对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);//绑定缓冲对象
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //绑定数据
    //绑定VAO
    glBindVertexArray(objectVAO);
    //设置和启用顶点属性指针
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    //设置和启用顶点属性指针
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);


    //创建灯光顶点数组对象
    unsigned int lightVAO; //灯光
    glGenVertexArrays(1, &lightVAO);
    glBindVertexArray(lightVAO);
// 只需要绑定VBO不用再次设置VBO的数据，因为箱子的VBO数据中已经包含了正确的立方体顶点数据
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 设置灯立方体的顶点属性（对我们的灯来说仅仅只有位置数据）
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);


    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
    /**
     * 设置图形模式:
     */
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); //线框模式
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); //填充模式
    //启用深度测试
    glEnable(GL_DEPTH_TEST);

    //设置着色器代码中变量的值
    //渲染循环 使得GLFW在退出之前一直保持运行
    while(!glfwWindowShouldClose(window))
    {
        float currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;
        // 输入控制
        processInput5(window);
        //渲染指令
        //当调用glClear函数，清除颜色缓冲之后，整个颜色缓冲都会被填充为glClearColor里所设置的颜色
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        //glClear(GL_COLOR_BUFFER_BIT);
        //清除深度缓冲（否则前一帧的深度信息仍然保存在缓冲中）
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glUseProgram(lightShader.id);
        setLightUniformValue(lightShader.id);
        glBindVertexArray(lightVAO);
        //立方体36个点
        glDrawArrays(GL_TRIANGLES, 0, 36);


        glUseProgram(objectShader.id);
        setObjectUniformValue(objectShader.id);
        glBindVertexArray(objectVAO);
        //立方体36个点
        glDrawArrays(GL_TRIANGLES, 0, 36);


        //检查并调用事件，交换缓冲
        glfwSwapBuffers(window); //交换颜色缓冲
        //检查触发事件 函数检查是否有触发事件，比如键盘、鼠标等信号输入，然后更新窗口状态，调用相应的回调函数（可通过回调方法手动设置）。
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &lightVAO);
    glDeleteVertexArrays(1, &objectVAO);
    glDeleteBuffers(1, &VBO);
    //释放/删除之前的分配的所有资源
    glfwTerminate();
}

int main(){

    show();
    return 0;
}
